﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine.Dtd.Schema;
using Engine.Sgml.Parser;

namespace Engine.Sgml.Decoding {
    internal sealed class MarkupReader {
        private char[] terminators;
        private EntityReader entityReader;
        public Stack<SName> elements;

        public MarkupReader() {
            this.elements = new Stack<SName>();
            this.entityReader = new EntityReader();
            this.terminators = new[] { Characters.EndTagMarkup, Characters.StartTagMarkup };
            }

        public void Read() {
            if (ReadState == SgmlParserState.Initial) {
                if (entityReader.Entity == null)
                    throw new InvalidOperationException("Entity value could not be null.");
                entityReader.ReadChar();
                }
            while (ReadInternal()) {
                switch (ReadState) {
                    case SgmlParserState.ReadEndElement:
                        SName endElement = ReadElement();
                        if (CurrentElement.Equals(endElement))
                            elements.Pop();
                        break;
                    case SgmlParserState.ReadStartElement:
                        SName element = ReadElement();
                        elements.Push(element);
                        break;
                    default:
                        throw new InvalidOperationException("Unexpected state of Markup reader in validation process.");
                    }
                }
            }

        private bool ReadInternal() {
            do {
                switch (entityReader.CurrentChar) {
                    case Characters.EndOfFile:
                        ReadState = SgmlParserState.EndOfFile;
                        return false;
                    case Characters.StartTagMarkup:
                        switch (entityReader.ReadChar()) {
                            case Characters.ExclamationPoint:
                                switch (entityReader.ReadChar()) {
                                    case Characters.CommentMarkup:
                                        ReadState = SgmlParserState.ReadComment;
                                        entityReader.ReadToEnd(terminators);
                                        break;
                                    case Characters.LeftBracket:
                                        ReadState = SgmlParserState.ReadConditionalBlock;
                                        entityReader.ReadToEnd(terminators);
                                        break;
                                    default:
                                        ReadState = SgmlParserState.ReadDocumentType;
                                        entityReader.ReadToEnd(terminators);
                                        break;
                                    }
                                break;
                            case Characters.ProcessingInstructionMark:
                                ReadState = SgmlParserState.ReadProcessingInstruction;
                                break;
                            case Characters.ClosingTagMarkup:
                                ReadState = SgmlParserState.ReadEndElement;
                                return true;
                            default:
                                ReadState = SgmlParserState.ReadStartElement;
                                return true;
                            }
                        break;
                    case Characters.Whitespace:
                    case Characters.HorizontalTab:
                    case Characters.CarriageReturn:
                    case Characters.LineFeed:
                        ReadState = SgmlParserState.ReadWhitespace;
                        entityReader.SkipWhitespace();
                        break;
                    default:
                        DElementDeclaration declaration = null;
                        if (declaration == null) {
                            ReadState = SgmlParserState.ReadText;
                            entityReader.ReadToEnd(terminators);
                            }
                        else
                            switch (declaration.ContentModel.ContentType) {
                                case DContentModelType.CData:
                                    ReadState = SgmlParserState.ReadCharacterData;
                                    ReadCharacterData();
                                    break;
                                default:
                                    ReadState = SgmlParserState.ReadText;
                                    entityReader.ReadToEnd(terminators);
                                    break;
                                }
                        break;
                    }
                if (entityReader.CurrentChar == Characters.EndTagMarkup)
                    entityReader.ReadChar();
                } while (true);
            }

        private SName ReadElement() {
            string name = entityReader.ScanNameToken();
            SName element = SName.Get(name);
            return element;
            }

        private void ReadCharacterData() {
            while (entityReader.ReadChar() != Characters.EndOfFile) {
                if (entityReader.CurrentChar == Characters.StartTagMarkup && entityReader.PeekChar() == Characters.ClosingTagMarkup) {
                    entityReader.ReadChar();
                    entityReader.ReadChar();
                    string elementName = entityReader.ReadNameToken();
                    if (elementName != null && entityReader.SkipWhitespace() == Characters.EndTagMarkup && CurrentElement.Equals(elementName)) {
                        entityReader.ReadChar();
                        break;
                        }
                    else
                        continue;
                    }
                else if (entityReader.CurrentChar == Characters.RightBracket && entityReader.PeekChar() == Characters.RightBracket) {
                    entityReader.ReadChar();
                    entityReader.ReadChar();
                    if (entityReader.CurrentChar == Characters.EndTagMarkup)
                        break;
                    }
                }
            }

        public SgmlParserState ReadState { get; private set; }

        public Entity Entity {
            get { return entityReader.Entity; }
            set {
                Guard.ArgumentNotNull(value, "Entity");
                entityReader.Initialize(value);
                }
            }

        public SName CurrentElement {
            get { return elements.Peek(); }
            }
        }
    }
